package com.spring.security.app.oauth2;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.spring.security.core.properties.SecurityConstants;
import com.spring.security.core.properties.SecurityProperties;
import com.spring.security.core.support.ServerResponseEntity;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.DefaultOAuth2RefreshToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.client.BaseClientDetails;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * token 刷新控制器
 *
 * @author seabed_moon
 */
@RestController
public class RefreshTokenController {

    private Logger LOGGER = LoggerFactory.getLogger(getClass());

    private final TokenStore redisTokenStore;

    private final SecurityProperties securityProperties;

    private final AuthorizationServerTokenServices authorizationServerTokenServices;

    private final ObjectMapper objectMapper;

    private final RedisTemplate<String, Object> redisTemplate;

    public RefreshTokenController(TokenStore redisTokenStore, SecurityProperties securityProperties, AuthorizationServerTokenServices authorizationServerTokenServices, ObjectMapper objectMapper, RedisTemplate<String, Object> redisTemplate) {
        this.redisTokenStore = redisTokenStore;
        this.securityProperties = securityProperties;
        this.authorizationServerTokenServices = authorizationServerTokenServices;
        this.objectMapper = objectMapper;
        this.redisTemplate = redisTemplate;
    }


    /**
     * 刷新token
     *
     * @param httpServletRequest
     * @param httpServletResponse
     * @throws IOException
     */
    @RequestMapping(value = SecurityConstants.REFRESH_TOKEN_URL, method = RequestMethod.POST)
    public void refreshToken(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException {
        LOGGER.info("refresh_token start");

        String grantType = httpServletRequest.getParameter(OAuth2Utils.GRANT_TYPE);

        Assert.notNull(grantType, "grant_type is not null");

        if (!StringUtils.equals(grantType, SecurityConstants.DEFAULT_PARAMETER_NAME_REFRESH_TOKEN)) {
            throw new UnsupportedGrantTypeException("Unknown grant_type");
        }

        String authorizationToken = httpServletRequest.getHeader(SecurityConstants.DEFAULT_PARAMETER_NAME_AUTHORIZATION);
        Assert.notNull(authorizationToken, "Header Authorization is not null");

        // ~ 查询当前登录状态
        OAuth2Authentication oAuth2AuthenticationToken = redisTokenStore.readAuthentication(StringUtils.substringAfter(authorizationToken, SecurityConstants.DEFAULT_BASIC));

        if (null == oAuth2AuthenticationToken || !oAuth2AuthenticationToken.isAuthenticated()) {
            httpServletResponse.getWriter().write(objectMapper.writeValueAsString(ServerResponseEntity.fail(HttpStatus.UNAUTHORIZED.value(), "invalid access_token", null)));
            return;
        }

        Authentication authentication = oAuth2AuthenticationToken.getUserAuthentication();

        // ~ 获取当前刷新token的凭证
        String refreshToken = httpServletRequest.getParameter(SecurityConstants.DEFAULT_PARAMETER_NAME_REFRESH_TOKEN);

        Assert.notNull(refreshToken, "refresh_token is not null");

        LOGGER.info("refresh_token:{}", refreshToken);
        OAuth2RefreshToken refreshTokens = new DefaultOAuth2RefreshToken(refreshToken);

        OAuth2RefreshToken auth2RefreshToken = redisTokenStore.readRefreshToken(refreshToken);
        LOGGER.info("auth2RefreshToken:{}", auth2RefreshToken);
        OAuth2Authentication redisOAuth2Authentication = redisTokenStore.readAuthenticationForRefreshToken(refreshTokens);

        String clientId = securityProperties.getOauth2().getClients()[0].getClientId();
        // ~ 保证当前登录状态有效
        if (null != redisOAuth2Authentication && redisOAuth2Authentication.isAuthenticated()) {

            OAuth2AccessToken oldAccessToken = redisTokenStore.getAccessToken(oAuth2AuthenticationToken);

            if (null != oldAccessToken) {
                // ~ 清除上次登录信息
                redisTokenStore.removeAccessToken(oldAccessToken);

                redisTokenStore.removeRefreshToken(oldAccessToken.getRefreshToken());

                redisTokenStore.removeAccessTokenUsingRefreshToken(oldAccessToken.getRefreshToken());
            }

            BaseClientDetails baseClientDetails = new BaseClientDetails();

            baseClientDetails.setClientId(clientId);

            ClientDetails clientDetails = baseClientDetails;

            OAuth2AccessToken refreshAccessToken = Oauth2Util.buildOauth2AccessToken(authorizationServerTokenServices, authentication, clientId, clientDetails);

            String mobile = objectMapper.readValue(objectMapper.writeValueAsString(authentication.getPrincipal()), Map.class).get(SecurityConstants.DEFAULT_PARAMETER_NAME_MOBILE).toString();
            String singleDeviceLogin = SecurityConstants.SINGLE_DEVICE_LOGIN + mobile;
            redisTemplate.opsForValue().set(singleDeviceLogin, refreshAccessToken.getValue(), refreshAccessToken.getExpiresIn(), TimeUnit.SECONDS);

            StringBuilder tokenBuilder = new StringBuilder();

            tokenBuilder.append(SecurityConstants.DEFAULT_PARAMETER_NAME_BEARER).append(refreshAccessToken.getValue());

            httpServletResponse.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);

            httpServletResponse.setHeader(SecurityConstants.DEFAULT_PARAMETER_NAME_AUTHORIZATION, tokenBuilder.toString());

            httpServletResponse.getWriter().write(objectMapper.writeValueAsString(ServerResponseEntity.success(refreshAccessToken, HttpStatus.OK.name())));

            return;
        } else {
            httpServletResponse.getWriter().write(objectMapper.writeValueAsString(ServerResponseEntity.fail(HttpStatus.UNAUTHORIZED.value(), "invalid refresh_token", null)));
            return;
        }
    }

}
